home *** CD-ROM | disk | FTP | other *** search
- /***************************************************************************
- * *
- * SktSocket.m *
- * Copyright 1992 by Nik A Gervae *
- * *
- * One of a set of three Objective-C classes (SktSocketManager, SktSocket, *
- * and SktSocketUser) which implement a convenient interface to Berkeley *
- * stream sockets under NeXTSTEP(r). See the accompanying class *
- * specifications (files with a .rtf or .spec suffix) for further *
- * information. *
- * *
- * NeXTSTEP is a registered trademark of NeXT Computer, Inc. *
- * *
- ****************************************************************************
- * *
- * LICENSE *
- * *
- * This program is free software; you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation. *
- * *
- * The program and this makefile are distributed in the hope that it will *
- * be useful, but are provided "AS IS" AND WITHOUT ANY WARRANTY; without *
- * any express or implied warranty of MERCHANTABILITY or FITNESS FOR A *
- * PARTICULAR PURPOSE. See the GNU General Public License for more details. *
- * Any use or distribution of the program and documentation must include *
- * appropriate copyrights to acknowledge Nik A. Gervae and the Free *
- * Software Foundation, Inc. *
- * *
- * You should have received a copy of the GNU General Public License *
- * along with this program; if not, write to the Free Software *
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *
- * *
- ****************************************************************************
- * *
- * VERSION HISTORY *
- * *
- * Version numbers are simply dates in the form YYYYMMDD. These represent *
- * the date that version was finished. Only significantly changed versions *
- * are reported here, or those versions requiring explanation of changes. *
- * There may be many interim stages between dated versions. *
- * *
- * DateVersion Primary Author Notes *
- * ----------- --------------- -------------------------------------------- *
- * 19920327 Nik A Gervae First released version *
- * 19920723 Nik A Gervae Actually released *
- * *
- ***************************************************************************/
-
- #import <errno.h>
- #import <limits.h>
- #import <stdio.h>
- #import <string.h>
-
- #import <sys/types.h>
- #import <sys/socket.h>
- #import <sys/time.h>
- #import <netinet/in.h>
- #import <netdb.h>
- #import <fcntl.h>
-
- #import "SktSocket.h"
-
-
- typedef struct {
- @defs(SktSocketUser)
- } sktsocketuser;
-
- /*
- * See +initialize below for info on these.
- */
- typedef id (*qOutputFunc)(id self, SEL _cmd, const char *output,
- long int length, ...);
- static qOutputFunc outputToSocket;
-
- /***************************************************************************
- * *
- * SktReportSocketMemError() *
- * *
- * Report appropriately based on whether the process is a server (has an *
- * SktSocketManager), or a client. *
- * *
- ***************************************************************************/
- void SktReportSocketMemError(id self, char *message) {
-
- id manager;
-
- manager = [self manager];
-
- if (manager) {
- [manager log:message, [self name], [self socketFd]];
- }
- else if (stderr) {
- fprintf(stderr, message, [self name], [self socketFd]);
- }
- return;
- }
-
- /***************************************************************************
- * *
- * These are the constant strings used. Feel free to translate them into *
- * your favorite language. Do be sure to keep all the % directives in *
- * place, or change the code that accesses these strings. *
- * *
- ***************************************************************************/
- #define STR_ErrorNoFd "ERROR (%s): "
- #define STR_ErrorWithFd "ERROR (%s %d): "
-
- #define STR_CantAccept STR_ErrorNoFd "accept() failed.\n"
- #define STR_CantSetFNDELAY STR_ErrorWithFd "fcntl() can\'t set no delay.\n"
- #define STR_CantSetOpts STR_ErrorWithFd "unable to set sender\'s " \
- "options.\n"
- #define STR_NewConnect "(%s) New connection from %s [%s] " \
- "(socketFd %d)\n"
- #define STR_UnknownParens "(unknown)"
- #define STR_Unknown "unknown"
- #define STR_CantOpenSocket STR_ErrorNoFd "could not open socket\n"
- #define STR_UnknownHost STR_ErrorNoFd "%s -- unknown host %s\n"
- #define STR_CantConnectToServer STR_ErrorWithFd \
- "could not connect to server\n"
- #define STR_ReadError STR_ErrorWithFd "read()\n"
- #define STR_NoMoreInput "(%s %d) readInput -- no more input\n"
- #define STR_WriteError STR_ErrorWithFd "write()\n"
-
- #define STR_ReallocFatalError STR_ErrorWithFd "zone realloc failed.\n"
- #define STR_MallocFatalError STR_ErrorWithFd "zone malloc failed.\n"
-
-
-
- @implementation SktSocket
-
- /***************************************************************************
- * *
- * +initialize *
- * *
- * This method caches the function pointers for the queueOutput:ofLength: *
- * method. Since these methods are covered by other methods, we want to *
- * avoid a message send each time a cover is used. This way, we only incur *
- * the cost of a function call. Hopefully that's faster. :-) *
- * *
- ***************************************************************************/
- + initialize
- {
- static BOOL initted = NO;
-
- /*
- * Only do this if we're initializing the class itself. Having a
- * subclass do it only resets the static variables, which is a waste.
- */
- if (NO == initted) {
- outputToSocket = (qOutputFunc)
- [[SktSocket class] instanceMethodFor:@selector(queueOutput:ofLength:)];
- initted = YES;
- }
- return self;
- }
-
- /***************************************************************************
- * *
- * -init *
- * *
- ***************************************************************************/
- - init
- {
- return [self notImplemented:_cmd];
- }
-
- /***************************************************************************
- * *
- * -_init *
- * *
- * Don't ever send this yourself. It simply provides common functionality *
- * for the other three init... methods. *
- * *
- * ERROR CONDITION: If the allocation of the output queue fails, nil is *
- * returned. *
- * *
- ***************************************************************************/
- - _init
- {
- [super init];
-
- socketFd = -1; // A Clearly invalid value, for now.
- manager = nil;
- user = nil;
- hostaddress = NULL;
- hostname = NULL;
-
- zone = [self zone];
-
- outputQueue = (char *)NXZoneMalloc(zone, OUTQSIZE);
- if (!outputQueue) {
- SktReportSocketMemError(self, STR_MallocFatalError);
- return [self free];
- }
- queueLength = 0;
-
- return self;
- }
-
- /***************************************************************************
- * *
- * -initOnFd:withManager:options: *
- * *
- * THIS IS ONE OF THREE DESIGNATED INITIALIZERS FOR THIS CLASS. DO NOT *
- * EVER USE -init AS A DESIGNATED INITIALIZER. *
- * *
- * This is the designated initializer for SktSocket objects in the server *
- * program. It initializes the socket to accept a connection on a file *
- * descriptor, and records the SktSocketManager for further communication. *
- * *
- * ERROR CONDITIONS: The following conditions cause a return of nil: *
- * *
- * - No manager provided. *
- * - The socket can't be created/accepted. *
- * - setSocketOptions: returns nil. *
- * - The FNDELAY option can't be set. *
- * *
- ***************************************************************************/
- - initOnFd:(int)serviceSocketFd withManager:(SktSocketManager *)aManager
- {
- struct sockaddr_in peername;
- struct hostent *peerinfo;
- int i;
-
- [self _init];
-
- /*
- * Do this right away! (We can't log if there's no manager.)
- */
- if (aManager) manager = aManager;
- else return [self free];
-
- /*
- * Accept the connection to get the file desctiptor.
- */
- i = sizeof(peername);
- socketFd = accept(serviceSocketFd, (struct sockaddr *) &peername, &i);
-
- if (0 > socketFd) {
- [manager log:STR_CantAccept, [[self class] name]];
- return [self free];
- }
-
- /*
- * Set options on the socket. We have to do this
- * AFTER the accept(), or accept() will return -1.
- */
- if (![self setSocketOptions:socketFd]) {
- [manager log:STR_CantSetOpts, [self name], socketFd];
- return [self free];
- }
-
- /*
- * Get the address of the connected host. If the string duplication
- * fails, report it, but don't return nil. (This may need changing.)
- */
- hostaddress = zoneStrdup(zone, (char *)inet_ntoa(peername.sin_addr));
- if (!hostaddress) {
- SktReportSocketMemError(self, STR_MallocFatalError);
- }
-
- /*
- * Now muck around to get the name. Copy the name of the connected
- * host. If the string duplication fails, report it, but don't
- * return nil. (This may need changing.)
- */
- /// I think there's a bug in here (call to gethostbyaddr()) somewhere.
- peerinfo = gethostbyaddr((char *)&peername.sin_addr,
- sizeof(peername.sin_addr), AF_INET);
-
- if (!peerinfo) hostname = NULL;
- else hostname = zoneStrdup(zone, peerinfo->h_name);
- if (!hostname) {
- SktReportSocketMemError(self, STR_MallocFatalError);
- }
-
- /*
- * Log the connection.
- */
- if (manager && [manager doesLog]) {
- [manager log:STR_NewConnect, [[self class] name],
- (hostname == NULL ? STR_UnknownParens : hostname),
- (hostaddress == NULL ? STR_Unknown : hostaddress),
- socketFd];
- }
- return self;
-
- } /*initOnFd:withManager:options:*/
-
- /***************************************************************************
- * *
- * -initOnHostname:andPort:options: *
- * *
- * THIS IS ONE OF THREE DESIGNATED INITIALIZERS FOR THIS CLASS. DO NOT *
- * EVER USE -init AS A DESIGNATED INITIALIZER. *
- * *
- * This is the designated initializer for SktSocket objects in a client *
- * program. It initializes the socket to request a connection to a host by *
- * name and port. *
- * *
- * ERROR CONDITIONS: The following conditions cause a return of nil: *
- * *
- * - No hostname provided, or host info can't be accessed. *
- * - The socket can't be created/connected. *
- * - setSocketOptions: returns nil. *
- * - The FNDELAY option can't be set. *
- * *
- ***************************************************************************/
- - initOnHostname:(char *)hostName andPort:(int)port
- {
- struct sockaddr_in server;
- struct hostent *host;
-
- if (!hostName) return [self free];
-
- [self _init];
-
- /*
- * Get a new file descriptor.
- */
- socketFd = socket(AF_INET, SOCK_STREAM, 0);
- if (0 > socketFd) {
- if (stderr) fprintf(stderr, STR_CantOpenSocket, [[self class] name]);
- return [self free];
- }
-
- /*
- * Try to get the host from the name.
- */
- server.sin_family = AF_INET;
- host = gethostbyname(hostName);
- if (!host) {
- if (stderr) fprintf(stderr, STR_UnknownHost, [[self class] name],
- hostName);
- return [self free];
- }
-
- /*
- * Now try to connect.
- */
- memcpy(&server.sin_addr, host->h_addr, host->h_length);
- server.sin_port = htons(port);
- if (0 > connect(socketFd, (struct sockaddr *)&server, sizeof(server))) {
- if (stderr) fprintf(stderr, STR_CantConnectToServer,
- [[self class] name], socketFd);
- return [self free];
- }
-
- /*
- * Set options on the socket. We have to do this
- * AFTER the connect(), or connect() will return -1.
- */
- if (![self setSocketOptions:socketFd]) {
- if (stderr) fprintf(stderr, STR_CantSetOpts, [self name], socketFd);
- return [self free];
- }
-
-
- /*
- * Save the host name and address. If a string duplication fails,
- * report it, but don't return nil. (This may need changing.)
- */
- hostname = zoneStrdup(zone, hostName);
- if (!hostname) SktReportSocketMemError(self, STR_MallocFatalError);
-
- hostaddress = zoneStrdup(zone, (char *)inet_ntoa(host->h_addr));
- if (!hostaddress) SktReportSocketMemError(self, STR_MallocFatalError);
-
- return self;
-
- } /*initOnHostname:andPort:options:*/
-
- /***************************************************************************
- * *
- * -initOnAddress:andPort:options: *
- * *
- * THIS IS ONE OF THREE DESIGNATED INITIALIZERS FOR THIS CLASS. DO NOT *
- * EVER USE -init AS A DESIGNATED INITIALIZER. *
- * *
- * This is the designated initializer for SktSocket objects in a client *
- * program. It initializes the socket to request a connection to a host by *
- * internet address (NULL-terminated string in dot notation and port. *
- * *
- * ERROR CONDITIONS: The following conditions cause a return of nil: *
- * *
- * - No host address provided, or host info can't be accessed. *
- * - The socket can't be created/connected. *
- * - setSocketOptions: returns nil. *
- * - The FNDELAY option can't be set. *
- * *
- ***************************************************************************/
- - initOnAddress:(char *)hostAddress andPort:(int)port
- {
- struct sockaddr_in server;
- long int hostaddr;
- struct hostent *host;
-
- [self _init];
-
- /*
- * Get a new file descriptor.
- */
- socketFd = socket(AF_INET, SOCK_STREAM, 0);
- if (0 > socketFd) {
- if (stderr) fprintf(stderr, STR_CantOpenSocket, [[self class] name]);
- [self free];
- }
-
- /*
- * Get host info based on the address.
- */
- hostaddr = inet_addr(hostAddress);
- host = gethostbyaddr((char *)&hostaddr, sizeof(hostaddr), AF_INET);
- if (!host && stderr) {
- if (stderr) fprintf(stderr, STR_UnknownHost, [[self class] name],
- hostAddress);
- return [self free];
- }
-
- /*
- * Now try to connect.
- */
- server.sin_family = AF_INET;
- memcpy(&server.sin_addr, host->h_addr, host->h_length);
- server.sin_port = htons(port);
- if (0 > connect(socketFd, (struct sockaddr *)&server, sizeof(server))) {
- if (stderr) fprintf(stderr, STR_CantConnectToServer,
- [[self class] name]);
- return [self free];
- }
-
- /*
- * Set options on the socket. We have to do this
- * AFTER the connect(), or connect() will return -1.
- */
- if (![self setSocketOptions:socketFd]) {
- if (stderr) fprintf(stderr, STR_CantSetOpts, [self name], socketFd);
- return [self free];
- }
-
- /*
- * Save the host name and address. If a string duplication fails,
- * report it, but don't return nil. (This may need changing.)
- */
- hostname = zoneStrdup(zone, host->h_name);
- if (!hostname) SktReportSocketMemError(self, STR_MallocFatalError);
-
- hostaddress = zoneStrdup(zone, hostAddress);
- if (!hostaddress) SktReportSocketMemError(self, STR_MallocFatalError);
-
- return self;
-
- } /*initOnAddress:andPort:options:*/
-
- /***************************************************************************
- * *
- * -setSocketOptions: *
- * *
- * This method tries to set the FNDELAY option on the socket so it can *
- * handle asynchronous writes. It may be overriden in subclasses to set *
- * other socket/file descriptor options via setsockopt/fcntl. *
- * *
- * If this method returns nil, then any initialization is aborted and the *
- * init... method will return nil. *
- * *
- ***************************************************************************/
- - setSocketOptions:(int)fd
- {
- if (-1 == fcntl(fd, F_SETFL, FNDELAY)) {
- if (manager) [manager log:STR_CantSetFNDELAY, [[self class] name], fd];
- if (stderr) fprintf(stderr, STR_CantSetFNDELAY, [[self class] name], fd);
- return nil;
- }
-
- return self;
- }
-
- /***************************************************************************
- * *
- * -close *
- * *
- * Properly closes the socket by notifying the SktSocketManager if there is *
- * one (which will send it a -free message), otherwise just freeing. *
- * *
- ***************************************************************************/
- - close
- {
- if (manager) [manager closeSocket:self];
- else return [self free];
-
- return nil; // Just in case;
- }
-
- /***************************************************************************
- * *
- * -free *
- * *
- * Really closes the socket. Does all the ususal stuff too. Does not free *
- * the user; it is responsible for noticing its socket is gone, and taking *
- * appropriate action. *
- * *
- ***************************************************************************/
- - free
- {
- if (hostaddress) NXZoneFree(zone, hostaddress);
- if (hostname) NXZoneFree(zone, hostname);
- if (outputQueue) NXZoneFree(zone, outputQueue);
-
- close(socketFd);
- if (user && self == [user socket]) [user setSocket:nil];
- return [super free];
- }
-
- /***************************************************************************
- * *
- * -setUser: *
- * *
- * Sets the user object used to that supplied. This method also mucks with *
- * the old and new users, to properly remove self as the old user's socket *
- * without causing an infinite loop. This is doable with methods, but the *
- * classes are so tightly bound, it's not really worth the code bulk or *
- * cost of a message send. *
- * *
- ***************************************************************************/
- - setUser:(SktSocketUser *)aUser
- {
- SktSocketUser *oldUser;
-
- oldUser = user;
- user = aUser;
-
- /*
- * We need to muck with pointers because messaging would cause an
- * infinite loop.
- */
- if (oldUser) ((sktsocketuser *)oldUser)->socket = nil;
- if (user) ((sktsocketuser *)user)->socket = self;
- return oldUser;
- }
-
- /***************************************************************************
- * *
- * -user *
- * *
- * Returns the user (an instance of SktSocketUser or a subclass) associated *
- * with this SktSocket. *
- * *
- ***************************************************************************/
- - (SktSocketUser *)user
- {
- return user;
- }
-
- /***************************************************************************
- * *
- * -socketFd *
- * *
- * Returns the file descriptor for the socket. *
- * *
- ***************************************************************************/
- - (int) socketFd
- {
- return socketFd;
- }
-
- /***************************************************************************
- * *
- * -manager *
- * *
- * If the SktSocket has a manager (which it does if it's on the server), *
- * this returns it. *
- * *
- ***************************************************************************/
- - (struct SktSocketManager *)manager
- {
- return manager;
- }
-
- /***************************************************************************
- * *
- * -hostaddress *
- * *
- * Returns the address of the host the SktSocket's process ison, in dot *
- * notation. *
- * *
- ***************************************************************************/
- - (const char *)hostaddress
- {
- return hostaddress;
- }
-
- /***************************************************************************
- * *
- * -hostname *
- * *
- * Returns the name of the host the SktSocket's process is on. *
- * *
- ***************************************************************************/
- - (const char *)hostname
- {
- return hostname;
- }
-
- /***************************************************************************
- * *
- * -readInput *
- * *
- * Performs a read(2) on the socketFd, and has the user, if any, queue the *
- * data read as input. If there is no user, or an error other than *
- * EWOULDBLOCK occurs, or if the socketFd is at EOF, return nil. *
- * *
- * All the log messages from this method are sent only to the manager; that *
- * only, only when the SktSocket is in a server process. *
- * *
- ***************************************************************************/
- - readInput
- {
- char buf[BUFSIZ];
- int bytesread = 0;
-
- if (![self user]) return nil;
-
- if (0 < (bytesread = read(socketFd, buf, BUFSIZ))) {
- [[self user] queueInput:(const char *)buf ofLength:bytesread];
- return self;
- }
- else if (0 > bytesread) {
- if (EWOULDBLOCK == errno) return self;
- else {
- if (manager) [manager log:STR_ReadError,
- [[self class] name], socketFd];
- else if (stderr) fprintf(stderr, STR_ReadError,
- [[self class] name], socketFd);
- return nil;
- }
- }
- else {
- if (manager && [manager doesLog]) {
- [manager log:STR_NoMoreInput, [[self class] name], socketFd];
- }
- return nil;
- }
- }
-
- /***************************************************************************
- * *
- * -queueOutput:ofLength: *
- * *
- * Adds output to the output queue and adjusts the length. *
- * *
- * ERROR CONDITION: If the reallocation fails, a message is logged, and nil *
- * is returned. The output queue won't exist if this method returns nil, *
- * so you should consider a nil return as a fatal condition and either free *
- * the object, or exit the program. *
- * *
- ***************************************************************************/
- - queueOutput:(const char *)output ofLength:(long int)length
- {
- unsigned int fullLength;
-
- /*
- * I hope nobody is this stupid.
- */
- if (0 >= length) return self;
-
- /*
- * Find out how much text we're dealing with, check if we need more space,
- * and smash 'em together. Also update the new queue length.
- */
- fullLength = length + queueLength;
-
- if (malloc_size(outputQueue) <= fullLength) {
- outputQueue = (char *)NXZoneRealloc(zone, outputQueue, fullLength);
- if (!outputQueue) {
- SktReportSocketMemError(self, STR_ReallocFatalError);
- queueLength = 0;
- return nil;
- }
- }
- memcpy(outputQueue+queueLength, output, length);
-
- queueLength = fullLength;
-
- return self;
- }
-
- /***************************************************************************
- * *
- * -queueOutputString: *
- * *
- * A convenience for queueing NULL-terminated strings. *
- * *
- ***************************************************************************/
- - queueOutputString:(const char *)aString
- {
- return (*outputToSocket)(self, @selector(queueOutput:ofLength:),
- aString, strlen(aString));
- }
-
- /***************************************************************************
- * *
- * -flushOutput *
- * *
- * Flushes the output queue to the socketFd. Doesn't try to write after an *
- * EWOULDBLOCK error occurs. *
- * *
- * All the log messages from this method are sent only to the manager; that *
- * only, only when the SktSocket is in a server process. *
- * *
- * ERROR CONDITION: If the reallocation fails, a message is logged, and nil *
- * is returned. The output queue won't exist if this method returns nil, *
- * so you should consider a nil return as a fatal condition and either free *
- * the object, or exit the program. *
- * *
- ***************************************************************************/
- - flushOutput
- {
- long int writeLength; // how much to write
- long int written; // how much written so far
- int writeResult; // how much written this write()
- extern int errno; // in case Something Bad happens
-
- writeLength = queueLength;
- written = 0;
-
- /*
- * Big loop until all output is writte, an EWOULDBLOCK occurs, or
- * an other error occurs.
- */
- while (written < writeLength) {
-
- /*
- * Be careful with the args to write here. It expects int's,
- * not long int's or differences thereof.
- */
- writeResult = write(socketFd, (outputQueue + written),
- INT_MAX <= (writeLength - written) ?
- INT_MAX : (writeLength - written));
-
- /*
- * If we get an EWOULDBLOCK, don't hang around trying to write; we'll
- * have another chance later. If the error is something else, log it.
- */
- if (0 > writeResult) {
-
- if (EWOULDBLOCK != errno) {
- if (manager)
- [manager log:STR_WriteError, [[self class] name], socketFd];
- else if (stderr)
- fprintf(stderr, STR_WriteError, [[self class] name], socketFd);
-
- break;
- }
- }
-
- written += writeResult;
-
- } /*while(written < writeLength)*/
-
-
- /*
- * Shrink the queue if it's empty and really big, or just pull it back.
- */
- if (written >= writeLength) {
- if (malloc_size(outputQueue) > OUTQSIZE) {
- outputQueue = (char *)NXZoneRealloc(zone, outputQueue, OUTQSIZE);
- if (!outputQueue) {
- SktReportSocketMemError(self, STR_ReallocFatalError);
- queueLength = 0;
- return nil;
- }
- }
- }
- else {
- memmove(outputQueue, outputQueue+written, writeLength-written+1);
- }
-
- /*
- * Update the queue length.
- */
- queueLength = writeLength - written;
-
- return self;
-
- } /*flushOutput*/
-
- /***************************************************************************
- * *
- * -purgeOutput *
- * *
- * Marks the queue as empty, and shrinks it if needed. *
- * *
- ***************************************************************************/
- - purgeOutput
- {
- queueLength = 0;
-
- if (malloc_size(outputQueue) > OUTQSIZE) {
-
- outputQueue = (char *)NXZoneRealloc(zone, outputQueue, OUTQSIZE);
-
- if (!outputQueue) {
- SktReportSocketMemError(self, STR_ReallocFatalError);
- queueLength = 0;
- return nil;
- }
- }
-
- return self;
- }
-
- @end /*implementation SktSocket*/
-
- /***************************************************************************
- ***************************************************************************/
-